package com.gitee.wsl.math.matrix


import com.gitee.wsl.math.matrix.MutableMatrix.Companion.buildMatrix
import com.gitee.wsl.math.matrix.MutableMatrix.Companion.matrix

//typealias M = MutableMatrix

/**
 * The matrix class.
 */
open class MutableMatrix: Matrix {

    constructor():super()

    constructor(rowCount: Int, colCount: Int):super(rowCount,colCount)

    /**
     * Constructor.
     *
     * @param values
     * the 2-d array of values
     */
    constructor(values: Array<FloatArray>) {
        set(values)
    }

    // ---------//
    // Setters //
    // ---------//
    /**
     * Set the value of element [r,c].
     *
     * @param r
     * the row index
     * @param c
     * the column index
     * @param value
     * the value
     * @throws MatrixException
     * for an invalid index
     */
    @Throws(MatrixException::class)
    open operator fun set(r: Int, c: Int, value: Float) {
        if (r < 0 || r >= m_nRows || c < 0 || c >= m_nCols) {
            throw MatrixException(MatrixException.INVALID_INDEX)
        }
        m_aValues[r][c] = value
    }

    /**
     * Set this matrix from a 2-d array of values. If the rows do not have the
     * same length, then the matrix column count is the length of the shortest
     * row.
     *
     * @param values
     * the 2-d array of values
     */
    protected open fun set(values: Array<FloatArray>) {
        m_nRows = values.size
        m_nCols = values[0].size
        m_aValues = values
        for (r in 1 until m_nRows) {
            m_nCols = m_nCols.coerceAtMost(values[r].size)
        }
    }

    /**
     * Set a row of this matrix from a row vector.
     *
     * @param rv
     * the row vector
     * @param r
     * the row index
     * @throws MatrixException
     * for an invalid index or an invalid vector size
     */
    @Throws(MatrixException::class)
    open fun setRow(rv: RowVector, r: Int) {
        if (r < 0 || r >= m_nRows) {
            throw MatrixException(MatrixException.INVALID_INDEX)
        }
        if (m_nCols != rv.m_nCols) {
            throw MatrixException(MatrixException.INVALID_DIMENSIONS)
        }
        for (c in 0 until m_nCols) {
            m_aValues[r][c] = rv.m_aValues[0][c]
        }
    }

    /**
     * Set a column of this matrix from a column vector.
     *
     * @param cv
     * the column vector
     * @param c
     * the column index
     * @throws MatrixException
     * for an invalid index or an invalid vector size
     */
    @Throws(MatrixException::class)
    open fun setColumn(cv: ColumnVector, c: Int) {
        if (c < 0 || c >= m_nCols) {
            throw MatrixException(MatrixException.INVALID_INDEX)
        }
        if (m_nRows != cv.m_nRows) {
            throw MatrixException(MatrixException.INVALID_DIMENSIONS)
        }
        for (r in 0 until m_nRows) {
            m_aValues[r][c] = cv.m_aValues[r][0]
        }
    }


    // -------------------//
    // Matrix operations //
    // -------------------//


    companion object{
        fun matrix(rowCount: Int,colCount: Int) = MutableMatrix(rowCount, colCount)

        fun matrix(rowValues: List<FloatArray>) = MutableMatrix(rowValues.toTypedArray())

        fun matrixRow(vararg rowValue:Float) = RowVector(rowValue)

        fun matrixColor(vararg rowValue:Float) = ColumnVector(rowValue)

        /**
         * Produces a matrix with this context and given dimensions.
         */
        fun buildMatrix(rows: Int, columns: Int, initializer: (i: Int, j: Int) -> Float): MutableMatrix{
            val mutableMatrix=MutableMatrix(rows, columns)
            (0 until rows).forEach {i->
                (0 until columns).forEach { j->
                    mutableMatrix[i, j] = initializer(i,j)
                }
            }
            return mutableMatrix
        }

        /**
         * Produces a point compatible with matrix space (and possibly optimized for it).
         */
         //fun buildVector(size: Int, initializer: (Int) -> Float): Point<T>

        fun empty(size:Int) = buildMatrix(size,size){i,j->0f}

        fun identity(size:Int) = buildMatrix(size,size){i,j-> if(i==j) 1f else 0f }


    }
}

 operator fun MutableMatrix.invoke(vararg initV:Float): MutableMatrix {
    val arraySize = initV.size
    val size =  kotlin.math.min(m_nCols*m_nRows,arraySize)
    (0 until m_nRows).forEach {i->
        (0 until m_nCols).forEach { j->
            val index = i*m_nCols+j
            if(index >= size ) return this
            this[i, j] = initV[index]
        }
    }
    return this
}

operator fun MutableMatrix.invoke(vararg initV:Int): MutableMatrix =invoke(*initV.map{it.toFloat()}.toFloatArray())

operator fun MutableMatrix.invoke(vararg initV:Double): MutableMatrix =invoke(*initV.map{it.toFloat()}.toFloatArray())


//OP
infix operator fun MutableMatrix.plus(other:MutableMatrix) = add(other)

infix operator fun MutableMatrix.minus(other:MutableMatrix) = subtract(other)

infix operator fun MutableMatrix.times(other:MutableMatrix) = multiply(other)

infix operator fun MutableMatrix.times(other:Float) = multiply(other)

infix operator fun MutableMatrix.times(other:RowVector) = multiply(other)

infix operator fun MutableMatrix.times(other:ColumnVector) = multiply(other)

/**
 * Computes the dot product of this matrix and another one.
 *
 * @receiver the multiplicand.
 * @param other the multiplier.
 * @return the dot product.
 */
 infix fun MutableMatrix.dot(other: MutableMatrix): MutableMatrix {
    require(m_nCols == other.m_nCols) { "Matrix dot operation dimension mismatch: ($m_nRows, $m_nCols) x (${other.m_nRows}, ${other.m_nCols})" }
    return buildMatrix(m_nRows, other.m_nCols) { i, j ->
            var res = 0f
            for (l in 0 until m_nCols) {
                res += this@dot[i, l] * other[l, j]
            }
            res
        }
}

operator fun MutableMatrix.unaryMinus(): MutableMatrix = buildMatrix(m_nRows, m_nCols) { i, j ->
    -get(i, j)
}

operator fun MutableMatrix.div(v: Float): MutableMatrix = buildMatrix(m_nRows, m_nCols) { i, j ->
    get(i, j)/v
}

 inline fun MutableMatrix.map(crossinline transform: (Float) -> Float) = buildMatrix(m_nRows, m_nCols) { i, j ->
        transform(get(i, j))
  }


 fun MutableMatrix.sum(): Float     = this.toList().sum()
 fun MutableMatrix.min(): Float?    = this.toList().minOrNull()
 fun MutableMatrix.max(): Float?    = this.toList().maxOrNull()
 fun MutableMatrix.average():Double = this.toList().average()


fun List<FloatArray>.toMatrix() = matrix(this)

fun Array<FloatArray>.toMatrix() = this.toList().toMatrix()

/**
 * 生成线性方程组
 */
fun List<FloatArray>.toLinearSystem() = LinearSystem(this.toTypedArray())

/**
 * 生成反转的矩阵。计算其行列式、范数和
 */
fun List<FloatArray>.toInvertibleMatrix() = InvertibleMatrix(this.toTypedArray())

fun List<Float>.toMatrixRow() = RowVector(this.toFloatArray())

fun List<Float>.toMatrixColumn() = ColumnVector(this.toFloatArray())

fun MutableMatrix.toMatrixSquare() = SquareMatrix(this)

fun SquareMatrix.toMatrixIdentity() = IdentityMatrix.convert(this)

/**
 * Constructor.
 *
 * @param msg
 * the error message
 */

class MatrixException(msg: String?) : Exception(msg) {
    companion object {
        const val INVALID_INDEX = "Invalid index."
        const val INVALID_DIMENSIONS = "Invalid matrix dimensions."
        const val ZERO_ROW = "Matrix has a zero row."
        const val SINGULAR = "Matrix is singular."
        const val NO_CONVERGENCE = "Solution did not converge."
    }
}


/**
 * Compute the machine epsilon for the float and double types, the largest
 * positive floating-point value that, when added to 1, results in a value equal
 * to 1 due to roundoff.
 */
object Epsilon {
    private var FLOAT_EPSILON = 0f
    private var DOUBLE_EPSILON = 0.0

    init {
        // Loop to compute the float epsilon value.
        var fTemp = 0.5f
        while (1 + fTemp > 1) fTemp /= 2f
        FLOAT_EPSILON = fTemp

        // Loop to compute the double epsilon value.
        var dTemp = 0.5
        while (1 + dTemp > 1) dTemp /= 2.0
        DOUBLE_EPSILON = dTemp
    }

    /**
     * Return the float epsilon value.
     *
     * @return the value
     */
    fun floatValue(): Float {
        return FLOAT_EPSILON
    }

    /**
     * Return the double epsilon value.
     *
     * @return the value
     */
    fun doubleValue(): Double {
        return DOUBLE_EPSILON
    }
}

